<!doctype html>
<html lang="zh-CN">
<head>
  <!--
    文件: 会员注册 - task01
    说明: 这是一个简单的会员注册页面示例，包含 HTML 表单、样式和前端验证逻辑。
    以下注释尽量详尽，逐行解释每个部分的用途与实现细节，便于学习与维护。
  -->
  <meta charset="utf-8">
  <!-- viewport 用于确保移动端显示比例正确 -->
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>会员注册 - task01</title>
  <style>
    /* ---------- 全局与容器样式 ---------- */
    /* body: 页面整体字体、背景和内边距设置 */
    body {
      font-family: Arial, Helvetica, "宋体", sans-serif; /* 首选英文字体，回退到宋体、无衬线 */
      background: #f7f7f7; /* 页面背景色 */
      padding: 24px; /* 页面边距，让 card 不紧贴窗口 */
    }

    /* card: 表单的容器，设置宽度、背景、圆角与阴影 */
    .card {
      width: 420px;
      background: #fff;
      border: 1px solid #ddd;
      border-radius: 6px;
      padding: 18px;
      margin: 0 auto; /* 居中 */
      box-shadow: 0 2px 6px rgba(0,0,0,0.03);
    }

    /* 标题样式 */
    h2 {
      color: #2b6db3;
      margin: 0 0 12px;
      font-size: 16px;
    }

    /* 每一行(row)使用 flex 布局，方便左右两列对齐 */
    .row {
      display: flex;
      align-items: center;
      margin: 8px 0;
    }

    /* label 固定宽度，能保证右侧表单控件对齐 */
    label {
      width: 90px;
      color: #333;
      font-size: 13px;
    }

    /* 通用输入、选择框与文本区样式 */
    input[type=text], input[type=password], input[type=email], select, textarea {
      flex: 1; /* 占据剩余空间 */
      padding: 6px 8px;
      border: 1px solid #ccc;
      border-radius: 3px;
      font-size: 13px;
    }

    /* 必填标记颜色 */
    .required {
      color: #d9534f;
      margin-left: 6px;
    }

    /* 验证码区域内部布局 */
    .captcha-box {
      display: flex;
      align-items: center;
    }

    /* 验证码样式：虚线边框、可点击刷新 */
    .captcha {
      display: inline-block;
      padding: 6px 10px;
      border: 1px dashed #9ab;
      border-radius: 4px;
      margin-left: 6px;
      font-weight: 700;
      letter-spacing: 2px;
      cursor: pointer;
      user-select: none; /* 防止选中文字 */
    }

    /* 刷新按钮样式 */
    .refresh {
      margin-left: 8px;
      padding: 6px 8px;
      border: 1px solid #ccc;
      background: #fafafa;
      border-radius: 4px;
      cursor: pointer;
    }

    /* 性别选择内的输入间距 */
    .gender input {
      margin-left: 6px;
      margin-right: 4px;
    }

    /* 多行文本框高度与可调整性 */
    textarea {
      height: 120px;
      resize: vertical; /* 只允许垂直方向缩放 */
    }

    /* 按钮组 */
    .buttons {
      display: flex;
      gap: 8px;
      justify-content: center; /* 居中按钮 */
      margin-top: 12px;
    }

    button {
      padding: 8px 14px;
      border-radius: 4px;
      border: 1px solid #bbb;
      background: #eee;
      cursor: pointer;
    }

    /* 头像预览固定大小并保持裁切 */
    .photo-preview {
      width: 120px;
      height: 120px;
      border: 1px solid #ddd;
      display: inline-block;
      object-fit: cover; /* 保持图片比例并裁剪 */
      margin-left: 6px;
    }

    /* 滑块所在行对齐 */
    .slider-row {
      align-items: center;
    }

    /* 滑块数值显示 */
    .slider-value {
      width: 60px;
      text-align: center;
      margin-left: 8px;
    }

    /* 错误提示样式 */
    small.error {
      color: #d9534f;
      display: block;
      margin-left: 90px; /* 与 label 对齐 */
    }
  </style>
</head>
<body>
  <div class="card">
    <h2>欢迎注册会员</h2>

    <!--
      表单说明:
      - id="regForm" 便于在脚本中获取表单元素
      - novalidate 禁用浏览器默认的内置表单提交时提示（我们在 JS 中手动调用 checkValidity() 并显示自定义提示）
    -->
    <form id="regForm" novalidate>

      <!-- 手机号码 -->
      <!--
        pattern="^\d{11}$"：用于匹配 11 位数字的正则。
      -->
      <div class="row">
        <label>手机号码<span class="required">必填</span></label>
        <input id="phone" name="phone" type="text" placeholder="11位手机号" pattern="^\d{11}$" required>
      </div>

      <!-- 密码 -->
      <!-- minlength="8"：最小长度限制（前端提示），真正安全策略还需在后端校验 -->
      <div class="row">
        <label>创建密码<span class="required">必填</span></label>
        <input id="pwd" name="password" type="password" placeholder="8位密码" minlength="8" required>
      </div>

      <!-- 邮箱 -->
      <!-- type="email" 会在 checkValidity() 中进行基本的 email 格式校验 -->
      <div class="row">
        <label>注册邮箱<span class="required">必填</span></label>
        <input id="email" name="email" type="email" placeholder="例如:wustzz@sina.com" required>
      </div>

      <!-- 验证码 -->
      <!--
        这里使用一个前端生成的验证码演示：
        - captcha 元素显示随机字符串
        - captchaInput 用户输入用于比对
        - 刷新按钮可重新生成
        生产环境建议使用后端生成并在服务端验证以防篡改
      -->
      <div class="row">
        <label>验证码<span class="required">必填</span></label>
        <div class="captcha-box">
          <!-- maxlength="4" 限制输入长度 -->
          <input id="captchaInput" name="captcha" type="text" maxlength="4" placeholder="4位" required style="width:80px;padding:6px 8px;border:1px solid #ccc;border-radius:3px">
          <span id="captcha" class="captcha" title="点击刷新验证码"></span>
          <button type="button" class="refresh" id="refreshBtn" title="刷新">刷新</button>
        </div>
      </div>

      <!-- 性别（单选） -->
      <div class="row gender">
        <label>性别</label>
        <div>
          <label><input type="radio" name="sex" value="男" checked> 男</label>
          <label><input type="radio" name="sex" value="女"> 女</label>
        </div>
      </div>

      <!-- 生日（这里是普通文本框，实际可替换为 date 类型或第三方日期选择器） -->
      <div class="row">
        <label>生日</label>
        <input type="text" placeholder="年 / 月 / 日">
      </div>

      <!-- 月薪滑块 -->
      <div class="row slider-row">
        <label>月薪</label>
        <input id="salary" type="range" min="0" max="20000" step="100" value="5000">
        <div class="slider-value">¥<span id="salaryVal">5000</span></div>
      </div>

      <!-- 个人照片（文件输入） -->
      <!-- accept="image/*" 限制只选图片文件 -->
      <div class="row">
        <label>个人照片</label>
        <div>
          <input id="photo" type="file" accept="image/*">
          <br>
          <!-- 预览图片元素，初始隐藏（display:none） -->
          <img id="preview" class="photo-preview" alt="预览" src="" style="display:none">
        </div>
      </div>

      <!-- 学历（下拉） -->
      <div class="row">
        <label>个人学历<span class="required">必填</span></label>
        <select name="education" id="education" required>
          <option value="">请选择...</option>
          <option value="初中">初中</option>
          <option value="高中">高中</option>
          <option value="专科">专科</option>
          <option value="本科">本科</option>
          <option value="研究生">研究生</option>
          <option value="博士">博士</option>
          <option value="博士后">博士后</option>
        </select>
      </div>

      <!-- 籍贯（省市联动） -->
      <div class="row">
        <label>籍贯</label>
        <div style="display:flex;align-items:center;gap:8px;">
          <select name="province" id="province">
            <option value="">省/自治区</option>
          </select>
          <select name="city" id="city">
            <option value="">市</option>
          </select>
        </div>
      </div>

      <!-- 个人爱好（多选复选框） -->
      <div class="row">
        <label>个人爱好</label>
        <div style="flex:1;display:flex;flex-wrap:wrap;gap:6px;">
          <label><input type="checkbox" name="hobbies[]" value="唱歌"> 唱歌</label>
          <label><input type="checkbox" name="hobbies[]" value="跳舞"> 跳舞</label>
          <label><input type="checkbox" name="hobbies[]" value="跑步"> 跑步</label>
          <label><input type="checkbox" name="hobbies[]" value="游泳"> 游泳</label>
          <label><input type="checkbox" name="hobbies[]" value="阅读"> 阅读</label>
          <label><input type="checkbox" name="hobbies[]" value="编程"> 编程</label>
          <label><input type="checkbox" name="hobbies[]" value="旅游"> 旅游</label>
        </div>
      </div>

      <!-- 个人简介 -->
      <div class="row">
        <label>个人简介</label>
        <textarea name="bio" placeholder="个人简介..."></textarea>
      </div>

      <!-- 错误提示区域（初始隐藏） -->
      <small id="formError" class="error" style="display:none"></small>

      <!-- 提交与重置按钮 -->
      <div class="buttons">
        <button type="submit">提交</button>
        <button type="reset">重填</button>
      </div>
    </form>
  </div>

  <script>
    /*
      以下脚本实现：
      - 生成并展示前端验证码（仅演示用途）
      - 表单各控件的交互（滑块数值同步、图片预览、联动下拉）
      - 表单提交时的自定义校验与提示

      注意：生产环境请将重要校验（如手机号、密码策略、验证码）放在服务端验证，前端只起到用户交互提示作用。
    */

    // 生成随机验证码函数：默认生成 len 位由数字与字母组成的字符串
    function randCaptcha(len = 4) {
      const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
      let s = '';
      for (let i = 0; i < len; i++) s += chars.charAt(Math.floor(Math.random() * chars.length));
      return s;
    }

    // 获取 DOM 元素的引用，便于后续操作
    const captchaEl = document.getElementById('captcha');
    const refreshBtn = document.getElementById('refreshBtn');
    const captchaInput = document.getElementById('captchaInput');
    const salary = document.getElementById('salary');
    const salaryVal = document.getElementById('salaryVal');
    const photo = document.getElementById('photo');
    const preview = document.getElementById('preview');
    const regForm = document.getElementById('regForm');
    const formError = document.getElementById('formError');
    const education = document.getElementById('education');
    const province = document.getElementById('province');
    const city = document.getElementById('city');

    // currentCaptcha 用于保存当前展示的验证码字符串，以便和用户输入比对
    let currentCaptcha = '';

    // setCaptcha: 生成一个随机验证码并显示到页面中，同时给验证码添加一个随机旋转角度用于简单的视觉扰动
    function setCaptcha() {
      currentCaptcha = randCaptcha(4); // 生成 4 位验证码
      captchaEl.textContent = currentCaptcha; // 将验证码放到 <span id="captcha"> 中
      // 随机旋转角度（仅做演示，避免每次样式完全一致）
      captchaEl.style.transform = 'rotate(' + (Math.random() * 8 - 4) + 'deg)';
    }

    // 初始化页面时立即设置一次验证码
    setCaptcha();

    // 点击验证码文本或刷新按钮都会重新生成验证码
    captchaEl.addEventListener('click', setCaptcha);
    refreshBtn.addEventListener('click', setCaptcha);

    // 滑块同步数值：当滑块变化时更新右侧显示文本
    salary.addEventListener('input', () => {
      salaryVal.textContent = salary.value;
    });

    // 图片预览：当用户选择文件时，使用 URL.createObjectURL 显示临时预览
    photo.addEventListener('change', () => {
      const f = photo.files && photo.files[0];
      if (!f) {
        // 没有选择文件，隐藏预览并清空 src
        preview.style.display = 'none';
        preview.src = '';
        return;
      }
      // 创建本地 URL 用于 img 元素预览（注意：若在高频率替换场景应手动 revokeObjectURL 释放内存）
      const url = URL.createObjectURL(f);
      preview.src = url;
      preview.style.display = 'inline-block';
    });

    // 省市数据：一个简单的对象映射，key 为省/直辖市，value 为该省的城市数组
    const cityData = {
      "北京市": ["东城区", "西城区", "朝阳区", "丰台区", "石景山区", "海淀区", "门头沟区", "房山区", "通州区", "顺义区", "昌平区", "大兴区", "怀柔区", "平谷区", "密云区", "延庆区"],
      "天津市": ["和平区", "河东区", "河西区", "南开区", "河北区", "红桥区", "东丽区", "西青区", "津南区", "北辰区", "武清区", "宝坻区", "滨海新区", "宁河区", "静海区", "蓟州区"],
      "河北省": ["石家庄市", "唐山市", "秦皇岛市", "邯郸市", "邢台市", "保定市", "张家口市", "承德市", "沧州市", "廊坊市", "衡水市"],
      "山西省": ["太原市", "大同市", "阳泉市", "长治市", "晋城市", "朔州市", "晋中市", "运城市", "忻州市", "临汾市", "吕梁市"],
      "内蒙古自治区": ["呼和浩特市", "包头市", "乌海市", "赤峰市", "通辽市", "鄂尔多斯市", "呼伦贝尔市", "巴彦淖尔市", "乌兰察布市", "兴安盟", "锡林郭勒盟", "阿拉善盟"],
      "辽宁省": ["沈阳市", "大连市", "鞍山市", "抚顺市", "本溪市", "丹东市", "锦州市", "营口市", "阜新市", "辽阳市", "盘锦市", "铁岭市", "朝阳市", "葫芦岛市"],
      "吉林省": ["长春市", "吉林市", "四平市", "辽源市", "通化市", "白山市", "松原市", "白城市", "延边朝鲜族自治州"],
      "黑龙江省": ["哈尔滨市", "齐齐哈尔市", "鸡西市", "鹤岗市", "双鸭山市", "大庆市", "伊春市", "佳木斯市", "七台河市", "牡丹江市", "黑河市", "绥化市", "大兴安岭地区"],
      "上海市": ["黄浦区", "徐汇区", "长宁区", "静安区", "普陀区", "虹口区", "杨浦区", "闵行区", "宝山区", "嘉定区", "浦东新区", "金山区", "松江区", "青浦区", "奉贤区", "崇明区"],
      "江苏省": ["南京市", "无锡市", "徐州市", "常州市", "苏州市", "南通市", "连云港市", "淮安市", "盐城市", "扬州市", "镇江市", "泰州市", "宿迁市"],
      "浙江省": ["杭州市", "宁波市", "温州市", "嘉兴市", "湖州市", "绍兴市", "金华市", "衢州市", "舟山市", "台州市", "丽水市"],
      "安徽省": ["合肥市", "芜湖市", "蚌埠市", "淮南市", "马鞍山市", "淮北市", "铜陵市", "安庆市", "黄山市", "滁州市", "阜阳市", "宿州市", "六安市", "亳州市", "池州市", "宣城市"],
      "福建省": ["福州市", "厦门市", "莆田市", "三明市", "泉州市", "漳州市", "南平市", "龙岩市", "宁德市"],
      "江西省": ["南昌市", "景德镇市", "萍乡市", "九江市", "新余市", "鹰潭市", "赣州市", "吉安市", "宜春市", "抚州市", "上饶市"],
      "山东省": ["济南市", "青岛市", "淄博市", "枣庄市", "东营市", "烟台市", "潍坊市", "济宁市", "泰安市", "威海市", "日照市", "临沂市", "德州市", "聊城市", "滨州市", "菏泽市"],
      "河南省": ["郑州市", "开封市", "洛阳市", "平顶山市", "安阳市", "鹤壁市", "新乡市", "焦作市", "濮阳市", "许昌市", "漯河市", "三门峡市", "南阳市", "商丘市", "信阳市", "周口市", "驻马店市", "济源市"],
      "湖北省": ["武汉市", "黄石市", "十堰市", "宜昌市", "襄阳市", "鄂州市", "荆门市", "孝感市", "荆州市", "黄冈市", "咸宁市", "随州市", "恩施土家族苗族自治州", "仙桃市", "潜江市", "天门市", "神农架林区"],
      "湖南省": ["长沙市", "株洲市", "湘潭市", "衡阳市", "邵阳市", "岳阳市", "常德市", "张家界市", "益阳市", "郴州市", "永州市", "怀化市", "娄底市", "湘西土家族苗族自治州"],
      "广东省": ["广州市", "韶关市", "深圳市", "珠海市", "汕头市", "佛山市", "江门市", "湛江市", "茂名市", "肇庆市", "惠州市", "梅州市", "汕尾市", "河源市", "阳江市", "清远市", "东莞市", "中山市", "潮州市", "揭阳市", "云浮市"],
      "广西壮族自治区": ["南宁市", "柳州市", "桂林市", "梧州市", "北海市", "防城港市", "钦州市", "贵港市", "玉林市", "百色市", "贺州市", "河池市", "来宾市", "崇左市"],
      "海南省": ["海口市", "三亚市", "三沙市", "儋州市", "五指山市", "琼海市", "文昌市", "万宁市", "东方市", "定安县", "屯昌县", "澄迈县", "临高县", "白沙黎族自治县", "昌江黎族自治县", "乐东黎族自治县", "陵水黎族自治县", "保亭黎族苗族自治县", "琼中黎族苗族自治县"],
      "重庆市": ["万州区", "涪陵区", "渝中区", "大渡口区", "江北区", "沙坪坝区", "九龙坡区", "南岸区", "北碚区", "綦江区", "大足区", "渝北区", "巴南区", "黔江区", "长寿区", "江津区", "合川区", "永川区", "南川区", "璧山区", "铜梁区", "潼南区", "荣昌区", "开州区", "梁平区", "武隆区", "城口县", "丰都县", "垫江县", "忠县", "云阳县", "奉节县", "巫山县", "巫溪县", "石柱土家族自治县", "秀山土家族苗族自治县", "酉阳土家族苗族自治县", "彭水苗族土家族自治县"],
      "四川省": ["成都市", "自贡市", "攀枝花市", "泸州市", "德阳市", "绵阳市", "广元市", "遂宁市", "内江市", "乐山市", "南充市", "眉山市", "宜宾市", "广安市", "达州市", "雅安市", "巴中市", "资阳市", "阿坝藏族羌族自治州", "甘孜藏族自治州", "凉山彝族自治州"],
      "贵州省": ["贵阳市", "六盘水市", "遵义市", "安顺市", "毕节市", "铜仁市", "黔西南布依族苗族自治州", "黔东南苗族侗族自治州", "黔南布依族苗族自治州"],
      "云南省": ["昆明市", "曲靖市", "玉溪市", "保山市", "昭通市", "丽江市", "普洱市", "临沧市", "楚雄彝族自治州", "红河哈尼族彝族自治州", "文山壮族苗族自治州", "西双版纳傣族自治州", "大理白族自治州", "德宏傣族景颇族自治州", "怒江傈僳族自治州", "迪庆藏族自治州"],
      "西藏自治区": ["拉萨市", "日喀则市", "昌都市", "林芝市", "山南市", "那曲市", "阿里地区"],
      "陕西省": ["西安市", "铜川市", "宝鸡市", "咸阳市", "渭南市", "延安市", "汉中市", "榆林市", "安康市", "商洛市"],
      "甘肃省": ["兰州市", "嘉峪关市", "金昌市", "白银市", "天水市", "武威市", "张掖市", "平凉市", "酒泉市", "庆阳市", "定西市", "陇南市", "临夏回族自治州", "甘南藏族自治州"],
      "青海省": ["西宁市", "海东市", "海北藏族自治州", "黄南藏族自治州", "海南藏族自治州", "果洛藏族自治州", "玉树藏族自治州", "海西蒙古族藏族自治州"],
      "宁夏回族自治区": ["银川市", "石嘴山市", "吴忠市", "固原市", "中卫市"],
      "新疆维吾尔自治区": ["乌鲁木齐市", "克拉玛依市", "吐鲁番市", "哈密市", "昌吉回族自治州", "博尔塔拉蒙古自治州", "巴音郭楞蒙古自治州", "阿克苏地区", "克孜勒苏柯尔克孜自治州", "喀什地区", "和田地区", "伊犁哈萨克自治州", "塔城地区", "阿勒泰地区", "石河子市", "阿拉尔市", "图木舒克市", "五家渠市", "北屯市", "铁门关市", "双河市", "可克达拉市", "昆玉市", "胡杨河市", "新星市", "白杨市"],
      "台湾省": ["台北市", "新北市", "桃园市", "台中市", "台南市", "高雄市", "基隆市", "新竹市", "嘉义市", "新竹县", "苗栗县", "彰化县", "南投县", "云林县", "嘉义县", "屏东县", "宜兰县", "花莲县", "台东县", "澎湖县", "金门县", "连江县"],
      "香港特别行政区": ["香港岛", "九龙", "新界", "离岛"],
      "澳门特别行政区": ["澳门半岛", "氹仔", "路环", "路氹城"]
    };

    // 将所有省份填充到省份下拉框中
    Object.keys(cityData).forEach(provinceName => {
      const option = document.createElement('option');
      option.value = provinceName;
      option.textContent = provinceName;
      province.appendChild(option);
    });

    // 当省份变化时，更新城市下拉框的选项
    province.addEventListener('change', () => {
      const selectedProvince = province.value;
      city.innerHTML = '<option value="">市</option>';
      if (selectedProvince && cityData[selectedProvince]) {
        cityData[selectedProvince].forEach(cityName => {
          const option = document.createElement('option');
          option.value = cityName;
          option.textContent = cityName;
          city.appendChild(option);
        });
      }
    });

    // 表单提交逻辑
    regForm.addEventListener('submit', (e) => {
      e.preventDefault(); // 阻止默认提交行为（页面刷新）
      formError.style.display = 'none';

      // 使用 HTML5 表单校验 API 检查基础约束（type、required、pattern、minlength 等）
      if (!regForm.checkValidity()) {
        formError.textContent = '请按要求填写必填项（手机号、密码、邮箱、学历）并保证格式正确。';
        formError.style.display = 'block';
        return;
      }

      // 自定义校验: 学历必选
      if (!education.value) {
        formError.textContent = '请选择您的学历。';
        formError.style.display = 'block';
        return;
      }

      // 验证码校验：简单的前端比对（演示）
      if (captchaInput.value !== currentCaptcha) {
        formError.textContent = '验证码错误，请重新输入或刷新。';
        formError.style.display = 'block';
        return;
      }

      // 所有校验通过后给用户友好提示（这里仅演示，实际需要提交到后端）
      alert('注册成功！表单已通过验证');

      // 提示后重置表单及部分交互状态
      regForm.reset();
      setCaptcha(); // 刷新验证码
      preview.style.display = 'none';
      preview.src = '';
      salaryVal.textContent = salary.value;
      city.innerHTML = '<option value="">市</option>';
    });

    // 重置表单时的额外处理（使用 setTimeout 模拟浏览器先完成 reset 后再做处理）
    regForm.addEventListener('reset', () => {
      setTimeout(() => {
        formError.style.display = 'none';
        preview.style.display = 'none';
        preview.src = '';
        setCaptcha();
        salaryVal.textContent = salary.value;
        city.innerHTML = '<option value="">市</option>';
      }, 0);
    });
  </script>
</body>
</html>
